using System;
using System.Collections.Generic;
using System.Threading;

using JetBrains.Annotations;

namespace Rsdn.SmartApp
{
	/// <summary>
	/// Simple implementation of <see cref="IObservable{T}"/>.
	/// </summary>
	public class Observable<T> : IObservable<T>, IDisposable
	{
		private readonly SortedDictionary<Key, IObserver<T>> _observers = new SortedDictionary<Key, IObserver<T>>();
		private readonly ReaderWriterLock _rwLock = new ReaderWriterLock();
		private bool _disposed;
		private int _keyCounter;

		/// <summary>
		/// Initialize instance.
		/// </summary>
		public IDisposable Subscribe([NotNull] IObserver<T> observer)
		{
			if (observer == null)
				throw new ArgumentNullException("observer");

			if (_disposed)
				throw new InvalidOperationException(LocalizableStrings.Observable_Subscribe_Observer_disposed);

			using (_rwLock.GetWriterLock())
			{
				var key = new Key(_keyCounter++);
				_observers.Add(key, observer);
				return DisposableHelper.CreateDisposable(() => Unsubscribe(key));
			}
		}

		private void Unsubscribe(Key key)
		{
			if (_disposed)
				return;
			using (_rwLock.GetWriterLock())
				_observers.Remove(key);
		}

		/// <summary>
		/// Fire item for all subscribers.
		/// </summary>
		public virtual void Fire(T value)
		{
			if (_disposed)
				throw new InvalidOperationException(LocalizableStrings.Observable_Subscribe_Observer_disposed);

			IObserver<T>[] observers;
			using (_rwLock.GetReaderLock())
				observers = _observers.Values.ToArray();

			foreach (var observer in observers)
				observer.OnNext(value);
		}

		/// <summary>
		/// Safe fire item for all subscribers.
		/// </summary>
		public virtual void SafeFire(T value, [NotNull] Action<Exception> failAction)
		{
			if (failAction == null)
				throw new ArgumentNullException("failAction");
			if (_disposed)
				throw new InvalidOperationException(LocalizableStrings.Observable_Subscribe_Observer_disposed);

			IObserver<T>[] observers;
			using (_rwLock.GetReaderLock())
				observers = _observers.Values.ToArray();

			foreach (var observer in observers)
				try
				{
					observer.OnNext(value);
				}
				catch (Exception ex)
				{
					failAction(ex);
				}
		}

		/// <summary>
		/// Dispose observable.
		/// </summary>
		public void Dispose()
		{
			if (_disposed)
				return;
			using (_rwLock.GetWriterLock())
			{
				foreach (var observer in _observers.Values)
					observer.OnCompleted();
				_observers.Clear();
				_disposed = true;
			}
		}

		#region Key class
		private class Key : IComparable<Key>
		{
			private readonly int _number;

			/// <summary>
			/// Initializes a new instance of the <see cref="T:System.Object"/> class.
			/// </summary>
			public Key(int number)
			{
				_number = number;
			}

			#region Implementation of IComparable<Observable<T>.Key>
			public int CompareTo(Key other)
			{
				return _number.CompareTo(other._number);
			}
			#endregion
		}
		#endregion
	}
}